﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;

namespace Mimoza.Transport
{
    class SocketStream : Stream
    {
        public SocketStream(Socket socket,int receiveTimeout)
        {
            m_socket = socket;
            m_recvTimeout = receiveTimeout;

            m_memoryStream = new MemoryStream();
        }

        public void StartPacketRecieve()
        {
            m_nextPacketEndPos = (-1);
        }

#region "Stream NotImplemented methods section"
        public override long Position
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override long Length
        {
            get { throw new NotImplementedException(); }
        }

        public override bool CanRead
        {
            get { throw new NotImplementedException(); }
        }

        public override bool CanWrite
        {
            get { throw new NotImplementedException(); }
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        public override void Flush()
        {
            throw new NotImplementedException();
        }
#endregion
        
        public override bool CanSeek
        {
            get { return false; }
        }

        

        public override int Read(byte[] buffer, int offset, int size)
        {
            if (!IsOnePacketRead())
            {
                ReadFromSocketToStream();

                return ReadOnePacket(buffer, offset, size);                
            }

            return 0;
        }

        public override void Write(byte[] buffer, int offset, int size)
        {
            // this is only for read
        }

        public bool HasUnprocessedData
        {
            get
            {
                if (m_memoryStream.Position < m_memoryStream.Length) return true; // memory stream already has datas
                else return false;
            }
        }

        bool IsOnePacketRead()
        {
            return ((m_nextPacketEndPos > 0) && m_memoryStream.Position>=m_nextPacketEndPos);
        }

        int ReadOnePacket(byte[] buffer, int offset, int size)
        {
            m_nextPacketEndPos = GetEndOfPacketPosition();

            int needToReadCount = m_nextPacketEndPos - (int)m_memoryStream.Position;
            if (needToReadCount > size) needToReadCount = size;

            int readCount = m_memoryStream.Read(buffer, 0, needToReadCount);
            return readCount;
        }

        void ReadFromSocketToStream()
        {
            if (HasUnprocessedData) return; // memory stream already has datas

            m_memoryStream.SetLength(0);

            const int buffSize = 8192;
            byte[] buff = new byte[buffSize];

            int pollTimeout = (m_recvTimeout > 0) ?
                    (m_recvTimeout * 1000) : Int32.MaxValue;
            while (m_socket.Poll(pollTimeout, SelectMode.SelectRead))
            {
                int recv = m_socket.Receive(buff);
                if (recv == 0)
                {
                    throw new System.Net.Sockets.SocketException();
                }

                m_totalRecv += recv;

                m_memoryStream.Write(buff, 0, recv);

                if (IsEndOfPacketReceived())
                {
                    m_memoryStream.Position = 0;
                    return;
                }
            }

            if (!m_bTimeoutElapsed) m_bTimeoutElapsed = true;
        }

        int GetEndOfPacketPosition()
        {
            if (m_nextPacketEndPos > 0) return m_nextPacketEndPos;

            byte[] buff = new byte[8192];

            long savePos = m_memoryStream.Position;

            int readCount = 0;
            int endPacketPos = (-1);
            long currentPos = 0;
            do
            {
                currentPos = m_memoryStream.Position;
                readCount = m_memoryStream.Read(buff, 0, buff.Length);
                endPacketPos = IndexOfBytes(buff, PacketEndToken, 0, readCount);
                string buffStr = Encoding.UTF8.GetString(buff, 0, buff.Length);
                if (endPacketPos >= 0) break;

                int nextPos = (int)m_memoryStream.Position - PacketEndToken.Length;
                m_memoryStream.Position = nextPos > 0 ? nextPos : 0;
            }
            while (readCount > 0);

            m_memoryStream.Position = savePos;

            return (int)currentPos + endPacketPos + PacketEndToken.Length;
        }

        bool IsEndOfPacketReceived()
        {
            int pos = (int)m_memoryStream.Length - PacketEndToken.Length;
            m_memoryStream.Position = pos>0 ? pos : 0;

            byte[] buff = new byte[PacketEndToken.Length];

            m_memoryStream.Read(buff, 0, buff.Length);

            if (IndexOfBytes(buff, PacketEndToken, 0, buff.Length) >= 0)
                return true;
            else
                return false;
        }

        static int IndexOfBytes(byte[] array, byte[] pattern, int startIndex, int count)
        {
            if (array == null || array.Length == 0 || pattern == null || pattern.Length == 0 || count == 0)
                return -1;

            int i = startIndex;
            int endIndex = count > 0 ? Math.Min(startIndex + count, array.Length) : array.Length;
            int fidx = 0;
            int lastFidx = 0;

            while (i < endIndex)
            {
                lastFidx = fidx;
                fidx = (array[i] == pattern[fidx]) ? ++fidx : 0;
                if (fidx == pattern.Length)
                {
                    return i - fidx + 1;
                }
                if (lastFidx > 0 && fidx == 0)
                {
                    i = i - lastFidx;
                    lastFidx = 0;
                }
                i++;
            }
            return -1;
        }


        byte[] PacketEndToken = System.Text.Encoding.ASCII.GetBytes(@"</Packet>");

        Socket  m_socket;
        MemoryStream m_memoryStream;

        int m_nextPacketEndPos = (-1);
        int m_recvTimeout = 0;
        int m_totalRecv = 0;
        bool m_bTimeoutElapsed = false;
    }
}
